package adc

import (
	"encoding/binary"
	"go.uber.org/zap"
	"sync"
	"wzgames/internal/admin/adcomm"
	"wzgames/internal/admin/model"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gvalidator"
	"wzgames/internal/response"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/common/version"
	"wzgames/pkg/logger"
	"wzgames/pkg/server/ws"
)

var (
	receiver *WsReceiver
	once     sync.Once
)

type WsReceiver struct {
	pingPong *sfs.RawPacket
	mu       sync.RWMutex
	admins   map[int64]*Admin

	msgHandler adcomm.WsCmdFn

	ppid *actor.PID
}

func NewWsReceiver(ppid *actor.PID) *WsReceiver {
	if receiver == nil {
		once.Do(func() {
			receiver = &WsReceiver{
				ppid: ppid,
				pingPong: &sfs.RawPacket{
					Id:        constant.ActionPingPong,
					ControlId: constant.SysControlID,
					Content:   make([]byte, 2),
				},
				admins: make(map[int64]*Admin),
			}
			binary.BigEndian.PutUint16(receiver.pingPong.Content, 0)
		})
	}

	return receiver
}

func GetReceiver() *WsReceiver {
	return receiver
}

func (mr *WsReceiver) CheckAdminActive() {
	mr.mu.RLock()
	defer mr.mu.RUnlock()
	for _, p := range mr.admins {
		actorservice.Send(p.Pid(), constant.ActiveCheck{})
	}
}

func (mr *WsReceiver) SetMsgHandler(fn adcomm.WsCmdFn) {
	mr.msgHandler = fn
}

func (mr *WsReceiver) CloseAdmin(adminId int64) {
	mr.mu.Lock()
	defer mr.mu.Unlock()
	delete(mr.admins, adminId)
}

func (mr *WsReceiver) StartAdmin(adm *model.Admin, token, clientToken string) {
	mr.mu.Lock()
	defer mr.mu.Unlock()
	admin, ok := mr.admins[adm.AdminID]
	if !ok {
		admin = NewAdmin(adm)
		mr.admins[adm.AdminID] = admin
	}
	admin.Login(token, clientToken)
	admin.UpdateMeta(adm)
}

func (mr *WsReceiver) GetAdmin(adminId int64) *Admin {
	mr.mu.RLock()
	defer mr.mu.RUnlock()
	if v, ok := mr.admins[adminId]; ok {
		return v
	}
	return nil
}

func (mr *WsReceiver) Receive(evt ws.Event) {
	defer logger.Recover()

	switch m := evt.Message().(type) {
	case *sfs.TSfsObject:
		if m.GetControlID() == constant.SysControlID && m.GetID() == constant.ActionPingPong { // ping pong
			evt.Session().Send(mr.pingPong)
			return
		}
		switch m.GetControlID() {
		case constant.SysControlID:
			actorservice.Send(mr.ppid, evt)
		case constant.ExControlID:
			hPid := evt.Session().LinkPid()
			if hPid != nil {
				actorservice.Send(hPid, evt)
			}
		default:
			logger.Warn("[ADC] illegal control id", zap.Uint8("ControlID", m.GetControlID()))
		}
	case ws.SessionAccepted:
	case ws.SessionClosed:
		hPid := evt.Session().LinkPid()
		if hPid != nil {
			actorservice.Send(hPid, constant.WSClosed{
				Handle: evt.Session().LinkID(),
				SessID: evt.Session().ID(),
				C:      m.C,
			})
		}
	default:
		logger.Error("[ADC] unknown client message type")
	}
}

func (mr *WsReceiver) HandleHandShake(sess ws.ISession, data *sfs.TSfsObject) {
	m := &sfs.RawPacket{
		Id:        constant.ActionHandShake,
		ControlId: constant.SysControlID,
	}
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteIntS(sfs.KeyCompressThreshold, int32(sfs.CompressionThreshold))
	xw.WriteIntS(sfs.KeyMaxMessageSize, int32(sfs.MaxMessageSize))
	//xw.WriteStringS(sfs.KeySessionToken, sess.Token())
	xw.WriteTo(m)

	sess.Send(m)
}

func (mr *WsReceiver) HandleLogin(sess ws.ISession, data *sfs.TSfsObject) {
	content := data.GetContent()
	if content == nil {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	paramObj := content.GetSFSObject(sfs.ParamId)
	if paramObj == nil {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	token := paramObj.GetUtfString(sfs.KeyToken)
	sessToken := paramObj.GetUtfString(sfs.KeySessToken)
	lang := paramObj.GetUtfString(sfs.KeyLang)
	ver := paramObj.GetUtfString(sfs.KeyVersion)
	platform := paramObj.GetSFSObject(sfs.KeyPlatform)
	if len(token) != 32 || len(sessToken) > 80 || len(sessToken) < 32 || platform == nil || !gvalidator.IsAlpha(lang) || len(lang) > 10 ||
		!gvalidator.IsAlphaNumeric(token) || !gvalidator.IsAlphaNumeric(sessToken) || !version.ValidSimpleVersionFormat(ver) {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	deviceType := platform.GetUtfString(sfs.KeyDeviceType)
	deviceInfo := platform.GetUtfString(sfs.KeyDeviceInfo)
	userAgent := platform.GetUtfString(sfs.KeyUserAgent)
	if !gvalidator.IsAvailableDevice(deviceType) || len(deviceInfo) > 1000 || len(userAgent) > 1000 {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}

	adminId, code := PP().CheckTicket(token + constant.Hyphen + sessToken)
	if code != 0 {
		mr.GenLoginErr(sess, code)
		return
	}

	admin := mr.GetAdmin(adminId)
	if admin == nil || !admin.WsLogin(sess, token, sessToken) {
		mr.GenLoginErr(sess, response.ErrUnAuthorize)
		return
	}
	mr.loginSuccess(sess, admin)
}

func (mr *WsReceiver) GenLoginErr(sess ws.ISession, code int16) {
	if sess == nil {
		return
	}
	pkg := &sfs.RawPacket{ControlId: constant.SysControlID, Id: constant.ActionLogin}
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteShortS(sfs.KeyErrorCode, code)
	xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})

	xw.WriteTo(pkg)
	sess.Send(pkg)
	sess.Acceptor().Server().RemoveSession(sess)
}

func (mr *WsReceiver) loginSuccess(sess ws.ISession, adm *Admin) {
	pkg := &sfs.RawPacket{ControlId: constant.SysControlID, Id: constant.ActionLogin}
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(6))
	xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
	xw.WriteStringS(sfs.KeyZoneName, adm.Name())
	xw.WriteLongS(sfs.KeyId, adm.AdminId())
	xw.WriteStringS(sfs.KeyUserName, adm.NickName())
	xw.WriteShortS(sfs.KeyPrivilegeId, 0)
	xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)

	xw.WriteTo(pkg)
	sess.Send(pkg)
}
